#include "IMLogic.h"

std::string sha256(const char *input, const size_t length)
{
    unsigned char hash[EVP_MAX_MD_SIZE];
    unsigned int lengthOfHash = 0;

    EVP_MD_CTX *ctx = EVP_MD_CTX_new();
    if (ctx == nullptr)
    {
        throw std::runtime_error("Failed to create EVP_MD_CTX");
    }

    if (EVP_DigestInit_ex(ctx, EVP_sha256(), nullptr) != 1)
    {
        EVP_MD_CTX_free(ctx);
        throw std::runtime_error("Failed to initialize EVP_MD_CTX with SHA-256");
    }

    if (EVP_DigestUpdate(ctx, input, length) != 1)
    {
        EVP_MD_CTX_free(ctx);
        throw std::runtime_error("Failed to update hash with data");
    }

    if (EVP_DigestFinal_ex(ctx, hash, &lengthOfHash) != 1)
    {
        EVP_MD_CTX_free(ctx);
        throw std::runtime_error("Failed to finalize hash");
    }

    EVP_MD_CTX_free(ctx);

    std::stringstream ss;
    for (unsigned int i = 0; i < lengthOfHash; ++i)
    {
        ss << std::hex << std::setw(2) << std::setfill('0') << (int)hash[i];
    }
    return ss.str();
}

void db_logic::Registet(const msg_c_s::registet &msg, std::function<void(unsigned int err)> call)
{
    IM_MYSQL_GET->async_query(
        (boost::format("select * from user where username = '%1%'") % msg.username).str(),
        [msg, call](const MysqlConnectionPool::error_code ec, const QueryResultPtr result)
        {
            if (ec)
            {
                LOG_E << "registet error: " << ec;
                call(ec);
                return;
            }
            if (result && result->getRowCount() > 0)
            {
                LOG_E << "registet error: "
                      << "user already exists";
                call(1);
                return;
            }
            auto password = sha256(msg.password, sizeof(msg.password));
            IM_MYSQL_GET->async_query(
                (boost::format("insert into user (username, password, nickname) values ('%1%', '%2%', '%3%')") % msg.username % password % msg.nickname).str(),
                [msg, call](const MysqlConnectionPool::error_code ec, const QueryResultPtr result)
                {
                    if (ec)
                    {
                        LOG_E << "registet error: " << ec;
                        call(ec);
                        return;
                    }
                    call(0);
                });
        });
    return;
}

void db_logic::Login(const msg_c_s::login &msg, const IMSessionPtr session, std::function<void(unsigned int err)> call)
{
    auto password = sha256(msg.password, sizeof(msg.password));

    IM_MYSQL_GET->async_query(
        (boost::format("select * from user where username = '%1%' and password = '%2%'") % msg.username % password).str(),
        [msg, session, call](const MysqlConnectionPool::error_code ec, const QueryResultPtr result)
        {
            if (ec)
            {
                LOG_E << "login error: " << ec;
                call(ec);
                return;
            }
            if (result && result->getRowCount() == 1)
            { // 登陆成功
                call(0);
                userid_t uid = result->ToUInt32("uid");
                auto u = IMUserManager::getInstance()->FindUser(uid);
                if (u)
                {
                    // 如果需要更新缓存的用户信息，则更新
                    // 发送好友信息
                    // 并给好友以及群组发送上线通知
                    return;
                }
                std::string nickname = result->ToString("nickname");
                std::string team_str = result->ToString("team");
                // 创建一个ptree对象
                boost::property_tree::ptree pt;
                // 创建一个stringstream对象
                std::stringstream ss;
                if (team_str == "")
                {
                    pt.put(IM_TEAM_NAME_DEFAULT, 0);
                    pt.put(IM_TEAM_NAME_BLACK, 255);
                }
                else
                {
                    ss.str(team_str);
                }
                boost::property_tree::read_json(ss, pt);

                u = std::make_shared<IMUser>(uid, msg.username, nickname, online_type_online, session);
                u->iniTeam(pt);
                // 登陆成功过后开始给客户端发送好友列表
                // 并给好友以及群组发送上线通知
                GetFriends(uid, true);
                return;
            }
            if (result && result->getRowCount() > 1)
            {
                LOG_E << "login error: "
                      << "user is not unique";
                call(2);
                return;
            }

            LOG_E << "login error: "
                  << "user not exists";
            call(1);
            return;
        });
    return;
}

void sendFriendMsg(const IMUserPtr u)
{
    // 发送好友列表
    msg_s_c::friendlist msg;
    auto teams = u->getTeams();
    auto friends = u->getFriends();
    for (auto &team : teams)
    {
        std::pair<u_int8_t, name_t> n;
        n.first = team.first;
        strcpy(n.second, team.second.c_str());
        msg.team_names.push_back(n);
    }
    for (auto &friend_ : friends)
    {
        msg_s_c::friend_info f;
        f.uid = friend_.user->getUid();
        strcpy(f.nickname, friend_.user->getNickname().c_str());
        strcpy(f.markname, friend_.mackName.c_str());
        f.status = friend_.user->getStatus();
        f.teamid = friend_.teamId;
        msg.friends.push_back(f);
    }
    auto team_size = msg.team_names.size();
    auto friend_size = msg.team_names.size();
    size_t len = sizeof(size_t) * 2 +
                 (sizeof(u_int8_t) + sizeof(name_t)) * team_size +
                 sizeof(msg_s_c::friend_info) * msg.friends.size();
    char buf[len];
    size_t offset = 0;
    memcpy(buf + offset, &team_size, sizeof(size_t));
    offset += sizeof(size_t);
    for (auto &team : msg.team_names)
    {
        memcpy(buf + offset, &team.first, sizeof(u_int8_t));
        offset += sizeof(u_int8_t);
        memcpy(buf + offset, team.second, sizeof(name_t));
        offset += sizeof(name_t);
    }
    memcpy(buf + offset, &friend_size, sizeof(size_t));
    offset += sizeof(size_t);
    for (auto &friend_ : msg.friends)
    {
        memcpy(buf + offset, &friend_.uid, sizeof(userid_t));
        offset += sizeof(userid_t);
        memcpy(buf + offset, friend_.nickname, sizeof(name_t));
        offset += sizeof(name_t);
        memcpy(buf + offset, friend_.markname, sizeof(name_t));
        offset += sizeof(name_t);
        memcpy(buf + offset, &friend_.status, sizeof(online_type));
        offset += sizeof(online_type);
        memcpy(buf + offset, &friend_.teamid, sizeof(u_int8_t));
        offset += sizeof(u_int8_t);
    }
    u->send(msg_type_getofriendlist, buf, len);
    // 并给好友以及群组发送上线通知
    // 好友
    for (auto &friend_ : friends)
    {
        auto fuser = friend_.user;
        if (fuser->getStatus() == online_type_online) // 在线 也可以通过session判断
        {
            msg_s_c::chat msg_chat;
            msg_chat.uid = u->getUid();
            msg_chat.msg = "上线了";
            // msg_s_c::userstatuschange msg;
            // msg.userid = u->getUid();
            // msg.status = u->getStatus();
            char chat_buf[sizeof(msg_s_c::chat) + msg_chat.msg.size()];
            fuser->send(msg_type_userstatuschange, (char *)&msg, sizeof(msg));
        }
    }

    // TODO 群组
}

void db_logic::GetFriends(const userid_t &uid, const bool &b_send)
{
    IM_MYSQL_GET->async_query(
        (boost::format("select * from friend where uid1 = '%1%' or uid2 = '%1%'") % uid).str(),
        [uid, b_send](const MysqlConnectionPool::error_code ec, const QueryResultPtr result)
        {
            if (ec)
            {
                LOG_E << "GetFriends error: " << ec;
                return;
            }
            if (result && result->getRowCount() > 0)
            {
                auto u = IMUserManager::getInstance()->FindUser(uid);
                auto friends = u->getFriends();
                friends.clear();
                std::map<userid_t, uint8_t> search_uids;
                while (result->isEnd())
                {
                    auto uid1 = result->ToUInt32("uid1");
                    auto uid2 = result->ToUInt32("uid2");
                    auto fuid = uid1 == uid ? uid2 : uid1;
                    // team1 存的是 uid1 的分组id
                    auto team1 = result->ToUInt8("team1");
                    // team2 存的是 uid2 的分组id
                    auto team2 = result->ToUInt8("team2");
                    auto teamid = uid1 == uid ? team1 : team2;
                    // 别名
                    auto mack1 = result->ToString("mack1");
                    auto mack2 = result->ToString("mack2");
                    auto mack = uid1 == uid ? mack1 : mack2;
                    auto fuser = IMUserManager::getInstance()->FindUser(fuid);
                    if (fuser)
                    {
                        friends.push_back({u->getTeamName(teamid), mack, teamid, fuser});
                    }
                    else
                    {
                        // 缓存里没有该用户信息 则需要从数据库中读取
                        search_uids[fuid] = teamid;
                    }
                    // friends.push_back({u->teams_name_[team1], team1, IMUserManager::getInstance()->FindUser(fuid)});
                    result->nextRow();
                }
                if (search_uids.size() > 0)
                {
                    std::string uid_list = "(";
                    for (auto &uid : search_uids)
                    {
                        uid_list += std::to_string(uid.first) + ",";
                    }
                    uid_list += ")";
                    std::string sql = (boost::format("select * from user where uid in %1%") % uid_list).str();
                    IM_MYSQL_GET->async_query(sql, [u, b_send](const MysqlConnectionPool::error_code ec, const QueryResultPtr result)
                                              {
                        if (ec)
                        {
                            LOG_E << "GetFriends error: " << ec;
                            return;
                        }
                        if (result && result->getRowCount() > 0)
                        {
                            while (result->isEnd())
                            {
                                auto uid = result->ToUInt32("uid");
                                auto username = result->ToString("username");
                                auto nickname = result->ToString("nickname");
                                auto fu = std::make_shared<IMUser>(uid, username, nickname, online_type_offline, nullptr);
                                IMUserManager::getInstance()->addUser(fu);
                                result->nextRow();
                            }
                        } 
                       if (b_send) sendFriendMsg(u); });
                }
                else
                {
                    if (b_send)
                        sendFriendMsg(u);
                }
            }
        });
}

void db_logic::GetGroup(const userid_t &uid, const bool &b_send)
{
    // TODO
}
